<?php
class Pageley_Gateway_Database implements Pageley_Gateway {

    private $dataSource;
    private $transactions;
    private $tables;
    private $systemSchema;
    private $lastIdSql;
    private $updateIdSql;
    private $commitIdGenerator;
    private $transactionCounter;

    public function __construct($dataSource, $systemSchema = "pageley_system", $commitIdGenerator = true) {
        $this->dataSource = $dataSource;
        $this->tables = new Pageley_Util_ArrayMap();
        $this->systemSchema = $systemSchema;
        $this->commitIdGenerator = $commitIdGenerator;
        $this->transactions = new Pageley_Util_ArrayMap();
        $this->transactionCounter = 0;
    }

    public function getTransaction($existing = true, $singleStatement = false) {
        $transaction = null;
    	if($existing && $this->transactions->size() > 0) {
	    	foreach($this->transactions as $t) {
	    		$transaction = $t;
	    		break;
	    	}
        } else {
        	$this->transactionCounter++;
        	$transaction = new Pageley_Gateway_Database_BasicTransaction(
                $this, $this->dataSource->getConnection(), 
                $this->transactionCounter, $singleStatement);
            $this->transactions->add($transaction);
        }
        return $transaction;
    }

	public function find($type, $id) {
        $table = $this->getTable($type);
        return $table->find($id);
    }
    
    public function findByProperty($type, $propertyName, $value) {
        $query = new Pageley_Query_Basic($type, 
            new Pageley_Query_Condition_Equals($propertyName, $value));
        $results = $this->query($query);
        $result = null;
        if($results->size() > 1) {
            throw new Pageley_Gateway_Database_Exception("Expected zero or one results for search on property '" .
                $propertyName . "', found " . $results->size() . ".");
        }
        if($results->size() == 1) {
            $result = $results->get(0);
        }
        return $result;
    }

	public function query($query) {
        $table = $this->getTable($query->getType());
        return $table->query($this->getDecoratedQuery($query));
    }

	public function count($query) {
        $table = $this->getTable($query->getType());
        return $table->count($this->getDecoratedQuery($query));
    }
    
    private function getDecoratedQuery($query) {
        if($query === null) {
			throw new Pageley_Gateway_Database_Exception("Query cannot be null.");
		}
        $table = $this->getTable($query->getType());
        return $this->dataSource->decorateQuery($query, $table);
    }

	public function insert($entity) {
        $table = $this->getTable($entity->getType());
        $table->insert($entity);
    }

	public function update($entity) {
        $table = $this->getTable($entity->getType());
        $table->update($entity);
    }

	public function delete($entity) {
        $table = $this->getTable($entity->getType());
        $table->delete($entity);
    }

    private function getTable($type) {
        $key = $type->getNamespace() . "." . $type->getName();
        $table = $this->tables->get($key);
        if($table === null) {
            if($type->getParentType() !== null) {
                $table = new Pageley_Gateway_Database_ExtendedTable($type, $this, $this->getTable($type->getParentType()));
            } else {
                $table = new Pageley_Gateway_Database_BasicTable($type, $this);
            }
            $this->tables->put($key, $table);
        }
        return $table;
    }

    public function generateId($table) {
        $transaction = $this->getTransaction(false);
        $connection = $transaction->getConnection();
        $tableName = $table->getFullTableName();
        $id = $this->getLastId($connection, $tableName);
        if($id != null) {
            $id += 1;
            $this->updateId($connection, $tableName, $id);
        } else {
            $id = 1;
            $this->insertId($connection, $tableName, $id);
        }
        if($table->find($id) != null) {
            $id = $table->getMaxId() + 1;
            $this->updateId($connection, $tableName, $id);
        	Pageley_Log::warn($this, "Invalid generator ID found for table '" . $tableName . "'. ID generator record corrected.");
        }
        if($this->commitIdGenerator) {
        	$transaction->commit();
        } else {
        	$transaction->rollback();
        }
        return $id;
    }

    private function getLastId($connection, $tableName) {
        $statement = $connection->prepareStatement("SELECT i.last_id FROM " . $this->systemSchema
            . ".id_generator i WHERE i.table_name = :table_name");
        $resultSet = $statement->execute(new Pageley_Util_ArrayMap(array("table_name" => $tableName)));
        $id = null;
        if($resultSet->hasNext()) {
        	$resultSet->next();
            $id = $resultSet->get("last_id");
        }
        return $id;
    }

    private function insertId($connection, $tableName, $id) {
    	$statement = $connection->prepareStatement("INSERT INTO " . $this->systemSchema . ".id_generator"
    			. " ( table_name, last_id ) VALUES ( :table_name, :last_id )");
        $statement->execute(new Pageley_Util_ArrayMap(array("table_name" => $tableName, "last_id" => $id)));
    }

    private function updateId($connection, $tableName, $id) {
    	$statement = $connection->prepareStatement($this->getUpdateIdSql());
        $statement->execute(new Pageley_Util_ArrayMap(array("table_name" => $tableName, "last_id" => $id)));
    }

    private function getLastIdSql() {
    	if($this->lastIdSql === null) {
    		$this->lastIdSql = "SELECT i.last_id FROM " . $this->systemSchema
            	. ".id_generator i WHERE i.table_name = :table_name";
    	}
    	return $this->lastIdSql;
    }

    private function getUpdateIdSql() {
    	if($this->updateIdSql === null) {
    		$this->updateIdSql = "UPDATE " . $this->systemSchema
            	. ".id_generator SET last_id = :last_id "
            	. "WHERE table_name = :table_name";
    	}
    	return $this->updateIdSql;
    }

    public function transactionComplete($transaction) {
        $this->transactions->removeValue($transaction);
    }
}